-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Staking #52
Staking #52
Conversation
We want to implement the ERC4626 tokenized vault standard based on OpenZeppelin contracts.
Based on [the solhint docs](https://github.com/protofire/solhint/blob/develop/docs/rules/security/func-visibility.md) This rule is required to be true for Solidity `>=0.7.0`.
The Acre contract is ERC4626 tokenized vault standard. Here we inherit from abstract contract `ERC4626` from OpenZeppelin lib. We are going to implement some custom features in follow-up work but the core is based on the ERC4626 standard.
Stakes a given amount of underlying token and mints shares to a receiver. This function calls `deposit` function from `ERC4626` contract. The function should accept `referrer` input parameter that will be later used for accounting (out of scope of this issue).
We want to support `receiveApproval`/`approveAndCall` pattern in the Acre contract. This package provides required interfaces to support this pattern.
The tBTC token contract that will be a staking token supports this pattern. To be able to stake in one transaction (instead of 2: approve + stake) we must implement the `RecieveApproval` interface. The token staking contract receives approval to spend tokens and create a stake for a given account.
Use this contract in unit tests as tBTC token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round
There is no need to add `super` keyword unless you want to override to function and call the original implementation.
We decided to use `Acre Staked Bitcoin` as a full name of the ERC4626 token.
Use `token` as it's how it is defined in the `IReceiveApproval` interface. It also doesn't collide with another property so the underscore is unnecessary.
This function is called by `receiveApproval` internally so we should use `public` instead of `external`.
Rename `token` -> `tbtc`.
`referrer` -> `referral`
We've updated the revert message in `stake` function when validating the `referral` param. Here we adjust the revert message in unit tests to the current version of contract.
Unit test stakig flow where there are more than one staker and the vault earns yield from strategies.
Instead of this require we want to emit an event that will include the referral. The off-chain accounting will need it to calculate the fee shares. To not duplicate the `Deposit` event from `ERC4626` the `Staked` event has only context for referral program. We can get other values from the `Deposit` event since it will be emitted in the same transaction.
This reverts commit f36c65c. To support receive approval pattern we need to override the `ERC4626.deposit` function to allow pass an `owner`. The OZ implementation sets the owner as `msg.sender` by default under the hood see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol#L178 and we can't change the owner. W/o this update using approve and call pattern to stake tokens, the `msg.sender` is tBTC token address, so we are actually trying to send tokens from the token contract to the receiver, which is impossible. We are going to address it in a separate PR.
`Token` -> `TestERC20` to be consistent.
Let's deploy the contracts first and then perform the minting.
It's worth to match the test name with the function name.
`TestToken` -> `TestERC20`. The file name should match the contract name.
To avoid a false-positive result where both contracts and tests calculations are wrong.
We validate the exact value in the next line so this check seems redundant.
`when staking` -> `when staking as a first staker`.
Define a variable `amountToStake` instead of passing number directly to functions - to improve readability.
Since the variable is named `amountToStake`, the amount we pass to `stake` function should be exactly `amountToStake`. Here we add a new variable `approvedAmount` which will be approved in the `before` hook and we use this value when defining the `amountToStake` which is equal `amountToStake + 1`. This will imporve readability.
We should use `expectedReceivedShares` while checking `stBTC` balance after staking.
Since the ERC4626 deposit function has different caller and receiver roles, let's test them here. This will reflect the flow for tBTC minting and staking in one transaction, where an external contract will call the stake function in the name of the staker.
Complicate the math by using `50` instead of `100` to use a different value than `100` which we already have in `totalShares` (25+75=100).
Add short description what is the purpose of Acre system.
The equality check is enough given we hardcode the expected values.
If the call reverts it will throw an exception in `beforeEach` hook for `await acre.connect(staker1).stake(...)`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no more comments. Leaving the final approval for Kuba.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left non-blocking comments.
}) | ||
|
||
context( | ||
"when a staker approved and staked tokens and wants to stake more but w/o another apporval", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"when a staker approved and staked tokens and wants to stake more but w/o another apporval", | |
"when a staker approved and staked tokens and wants to stake more but w/o another approval", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
) | ||
}) | ||
|
||
context("when there are two stakers, A and B ", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context("when there are two stakers, A and B ", () => { | |
context("when there are two stakers", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it("should stake tokens correctly", async () => { | ||
await expect( | ||
acre | ||
.connect(staker1) | ||
.stake(staker1AmountToStake, staker1.address, referral), | ||
).to.be.not.reverted | ||
}) | ||
|
||
it("should receive shares equal to a staked amount", async () => { | ||
const shares = await acre.balanceOf(staker1.address) | ||
|
||
expect(shares).to.eq(staker1AmountToStake) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could simplify this test:
it("should stake tokens correctly", async () => { | |
await expect( | |
acre | |
.connect(staker1) | |
.stake(staker1AmountToStake, staker1.address, referral), | |
).to.be.not.reverted | |
}) | |
it("should receive shares equal to a staked amount", async () => { | |
const shares = await acre.balanceOf(staker1.address) | |
expect(shares).to.eq(staker1AmountToStake) | |
}) | |
it("should receive shares equal to a staked amount", async () => { | |
const tx = acre | |
.connect(staker1) | |
.stake(staker1AmountToStake, staker1.address, referral) | |
await expect(tx).to.changeTokenBalances( | |
acre, | |
[staker1.address], | |
[staker1AmountToStake], | |
) | |
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depends on #52 Acre contract requires tBTC address as argument. We add `00_resolve_tbtc.ts` script that will ensure tBTC deployment artifact reference is available based on the network. For networks marked with `allowStubs` tag (hardhat local network for running tests) a stub ERC20 contract will be deployed. Added a script `./scripts/fetch_external_artifacts.sh` that downloads TBTC token deployment artifact from NPM packages for sepolia and mainnet network and places them under `./external` directory where they will be used for contracts deployment.
Closes: #25
This PR adds the initial implementation of the
stake
function. A staker should callstake
function to stake ERC20 token (tBTC) in the main Acre contract. In return the receiver will receive ERC4626 reward-bearing token (stBTC). The function should accept referral input parameter that will be later used for accounting (out of scope of this issue).